#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/sysinfo.h>
#include <sys/epoll.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "httpd.h"

#define DEFAULT_PORT 8080       //设置服务器端口
#define MAX_EVENT_NUM 1024      //设置epoll的最大枝叶
#define INFTIM -1           //返回-1的时候用

static int die = 0;     //统计挂了多少个进程

void process(int);      //one orocess one epoll

void handle_subprocess_exit();       //处理子进程退出

int main(int argc, char *argv[])  
{
    struct sockaddr_in server_addr;
    int listen_fd;
    int cpu_core_num;       //获取cpu数量，实现最大并行
    int on = 1;
    
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(listen_fd, F_SETFL, O_NONBLOCK);                              //非阻塞fd
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));       //端口复用

    bzero(&server_addr, sizeof(server_addr));       //清空serv_addr地址 对比 memset()
    server_addr.sin_family = AF_INET;               //ipv4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);        //任意IP可连接，注意大小端转换
    server_addr.sin_port = htons(DEFAULT_PORT);             //端口8080

    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) //绑定
    {
        perror("bind error, message: ");
        exit(1);
    }

    if (listen(listen_fd, 5) == -1)         //监听，同时请求链接不超过5个
    {
        perror("listen error, message: ");
        exit(1);
    }

    //printf("listening 8080\n");

    signal(SIGCHLD, handle_subprocess_exit); //注册子进程退出的信号

    cpu_core_num = get_nprocs();                //获取CPU数量
    //printf("cpu core num: %d\n", cpu_core_num);
    for (int i = 0; i < cpu_core_num; i++)      //创建CPUnumber个工作进程 + 空循环的父进程，充分利用cpu
    {
        pid_t pid = fork();
        if (pid == 0)                                //子进程入口
        {
            process(listen_fd);
            exit(0);
        }
    }

    while (1)                                   //父进程 空循环
    {
        if (die > 0)
        {
            pid_t pid = fork();             //子进程异常退出后，父进程记录其数量并创建新的子进程
            if (pid == 0)
            {
                process(listen_fd);
                exit(0);
            }
            die--;
        }
        sleep(1);
    }

    return 0;
}

void process(int listen_fd)                 //one process one epoll
{
    int conn_fd;
    int ready_fd_num;
    struct sockaddr_in client_addr;
    int client_addr_size = sizeof(client_addr);
    char buf[128];

    struct epoll_event ev, events[MAX_EVENT_NUM];
    int epoll_fd = epoll_create(MAX_EVENT_NUM);     //epoll_create    
    if (epoll_fd < 0)
    {
        perror("epoll_create error");
        close(listen_fd);
        return -1;
    }

    ev.data.fd = listen_fd;             //监听文件描述符的结点
    ev.events = EPOLLIN | EPOLLET;        //读事件 垂直触发

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1)       //监听的上树
    {
        perror("epoll_ctl error, message: ");
        exit(1);
    }

    while(1) 
    {
        ready_fd_num = epoll_wait(epoll_fd, events, MAX_EVENT_NUM, INFTIM);     //阻塞等待就绪队列events
        printf("[pid %d] 进程唤醒：\n", getpid());
        if (ready_fd_num == -1) 
        {
            perror("epoll_wait error, message: ");
            continue;
        }
        for(int i = 0; i < ready_fd_num; i++)                   //处理 有响应的事件结点
        {
            if (events[i].data.fd == listen_fd)             //如果是新链接请求到来
            {
                conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_size);//accept
                if (conn_fd == -1) 
                {
                    sprintf(buf, "[pid %d]accept 出错了: ", getpid());
                    perror(buf);
                    continue;
                }

                if (fcntl(conn_fd, F_SETFL, fcntl(conn_fd, F_GETFD, 0) | O_NONBLOCK) == -1)         //设置为非阻塞fd，先获取原fd的属性，再用|进行属性添加
                {
                    continue;
                }

                ev.data.fd = conn_fd;
                ev.events = EPOLLIN | EPOLLET;        //读事件
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1)     //通信文件描述符上树
                {
                    perror("epoll_ctl error, message: ");
                    close(conn_fd);
                }
                printf("[pid %d] 与 %s:%d 建立链接\n", getpid(), inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
            } 
            else if (events[i].events & EPOLLIN)              //有数据到来
            {
                printf("[pid %d] 处理 %s:%d 的请求\n", getpid(), inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
                conn_fd = events[i].data.fd;
                accept_request(conn_fd, &client_addr);      //处理数据
                close(conn_fd);                             //默认关闭文件描述符，epoll结点自动删除
            } 
            else if (events[i].events & EPOLLERR)       //错误事件.
            {
                fprintf(stderr, "epoll error\n");           //标准输出错误
                close(conn_fd);
            }
        }
    }
}

void handle_subprocess_exit()
{
    printf("clean subprocess.\n");
    int status;  
    while (waitpid(-1, &status, WNOHANG) > 0)  //循环回收 子进程
    {
        die++;                      //记录挂了几个子进程
    }

}